#include "instructions.h"
#include "pvm.h"

/**
 * Emuler l'opération LOAD de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool load(PVM *pvm, REG *dest, Mot source)
{
	*dest = source.brut;
	return true;
}

/**
 * Emuler l'opération STORE de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returnstrue pour vérifier la bonne éxecution du programme
 *
 */
bool store(PVM *pvm, REG *dest, Mot source)
{
	*dest = source.brut;
	return true;
}

/**
 * Emuler l'opération ADD de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returnstrue pour vérifier la bonne éxecution du programme
 *
 */
bool add(PVM *PVM, REG *dest, Mot source)
{
	*dest += source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération SUB de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool sub(PVM *PVM, REG *dest, Mot source)
{
	*dest -= source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération JMP de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param dest		le registre destination
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool jmp(PVM *PVM, REG *dest, Mot source)
{
	if (!verifierAccesMemoire(&source.brut))
		return false;

	if (source.brut == PVM->pc || source.brut == PVM->pc - 1)
		printf("Boucle infinie");
	PVM->pc = source.brut - 1;
	return true;
}

/**
 * Emuler l'opération JEQ de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool jeq(PVM *PVM, REG *dest, Mot source)
{
	if (PVM->sr == 0)
		return jmp(PVM, dest, source);
	return true;
}

/**
 * Emuler l'opération PUSH de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool push(PVM *PVM, REG *dest, Mot source)
{
	REG newSp = PVM->sp + SP_INCR;

	if ((!verifierAccesMemoire(&PVM->sp)) || (! verifierAccesMemoire(&newSp)))
		return false;

	PVM->mem[PVM->sp] = source;
	PVM->sp = newSp;
	return true;
}

/**
 * Emuler l'opération POP de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool pop(PVM *PVM, REG *dest, Mot source)
{
	REG newSp = PVM->sp - SP_INCR;

	if (!verifierAccesMemoire(&newSp))
		return false;

	*dest = PVM->reg[PVM->mem[newSp].codage.dest];
	PVM->sp = newSp;
	return true;
}

/**
 * Emuler l'opération CALL de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool call(PVM *PVM, REG *dest, Mot source)
{
	if (!push(PVM, dest, (Mot) PVM->pc))
		return false;

	return jmp(PVM, dest, source);
}

/**
 * Emuler l'opération RET de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool ret(PVM *PVM, REG *dest, Mot source)
{
	return pop(PVM, &PVM->pc, source);
}

/**
 * Emuler l'opération HALT de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @return true pour vérifier la bonne éxecution du programme
 *
 */
bool halt(PVM *PVM, REG *dest, Mot source)
{
	printf("HALT instruction, programme terminé");
	return true;
}

//---------------------------------------------------------------------------------------//
// -------------------- COMMANDES SUPPLEMENTAIRES IMPLEMENTEES --------------------------//
//---------------------------------------------------------------------------------------//

/**
 * Emuler l'opération MOV de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returnstrue pour vérifier la bonne éxecution du programme
 *
 */
bool mov(PVM *PVM, REG *dest, Mot source)
{
	*dest = source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération AND de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool and(PVM *PVM, REG *dest, Mot source)
{
	*dest &= source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération OR de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool or(PVM *PVM, REG *dest, Mot source)
{
	*dest |= source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération SHL de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool shl(PVM *PVM, REG *dest, Mot source)
{
	*dest <<= source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération SHR de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool shr(PVM *PVM, REG *dest, Mot source)
{
	*dest >>= source.brut;
	PVM->sr = *dest;
	return true;
}

/**
 * Emuler l'opération CMP de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool cmp(PVM *PVM, REG *dest, Mot source)
{
	PVM->sr = (*dest - source.brut);
	return true;
}

/**
 * Emuler l'opération DEC de PROCSI
 *
 * @param pvm 		la pvm cible
 * @param dest		le registre destination
 * @param source	le mot source
 *
 * @returns true pour vérifier la bonne éxecution du programme
 *
 */
bool dec(PVM *PVM, REG *dest, Mot source)
{
	--(*dest);
	PVM->sr = *dest;
	return true;
}

/**
 * Décrire l'ensemble des opérations possibles que la PVM puise traiter
 *
 */
Instruction tabInstructions[]  = {
		[LOAD]	= {&load, true, true},
		[STORE]	= {&store, true, true},
		[ADD]	= {&add, true, true},
		[SUB]	= {&sub, true, true},
		[JMP]	= {&jmp, false, true},
		[JEQ]	= {&jeq, false, true},
		[CALL]	= {&call, false, true},
		[RET]	= {&ret, false, false},
		[PUSH]	= {&push, false, true},
		[POP]	= {&pop, true, false},
		[HALT]	= {&halt, false, false},
		[MOV]	= {&mov, true, true},
		[AND]	= {&and, true, true},
		[OR]	= {&or, true, true},
		[SHL]	= {&shl, true, true},
		[SHR]	= {&shr, true, true},
		//[CMP]	= {&cmp, true, true},
		//[DEC] = &dec
};

/**
 * Récupérer l'instruction souhaitée
 *
 * @param m		le mot cible
 *
 * @returns l'instruction cible
 *
 */
Instruction getInstruction(const Mot m)
{
	return tabInstructions[m.codage.codeop];
}
